package cn.com.idmy.auth.rbac;

import io.lettuce.core.RedisException;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.convert.ConvertUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Collections;

@Slf4j
@Component
@RequiredArgsConstructor
@Accessors(fluent = true)
public class RedisRbacDaoImpl<ID> implements RbacDao<ID> {
    protected final StringRedisTemplate stringRedisTemplate;

    @Override
    public @NotNull Collection<String> listPermissions(@NotNull ID id, long accountType) {
        return Collections.emptySet();
    }

    @Override
    public @NotNull Collection<String> listRoles(@NotNull ID id, long accountType) {
        return Collections.emptySet();
    }

    @Override
    public boolean hasAllRoles(@NotNull ID id, long accountType, @NotNull Collection<String> roles) {
        return hasAll(id, accountType, roles, true);
    }

    @Override
    public boolean hasAnyRoles(@NotNull ID id, long accountType, @NotNull Collection<String> roles) {
        return hasAny(id, accountType, roles, true);
    }

    @Override
    public boolean hasAllPermissions(@NotNull ID id, long accountType, @NotNull Collection<String> permissions) {
        return hasAll(id, accountType, permissions, false);
    }

    @Override
    public boolean hasAnyPermissions(@NotNull ID id, long accountType, @NotNull Collection<String> permissions) {
        return hasAny(id, accountType, permissions, false);
    }

    @Override
    public void clearRoles(@NotNull ID id, long accountType) {
        stringRedisTemplate.delete(buildCacheKey(id, accountType, true));
    }

    @Override
    public void clearPermissions(@NotNull ID id, long accountType) {
        stringRedisTemplate.delete(buildCacheKey(id, accountType, false));
    }

    protected String buildCacheKey(@NotNull ID id, long accountType, boolean isRole) {
        return StrUtil.format("{}:rbac:{}:{}", accountType, isRole ? "role" : "permission", id);
    }

    protected void add(@NotNull String cacheKey, @NotNull Collection<String> values) {
        stringRedisTemplate.delete(cacheKey);
        stringRedisTemplate.opsForSet().add(cacheKey, ConvertUtil.toStrArray(values));
    }

    protected boolean hasAll(@NotNull ID id, long accountType, @NotNull Collection<String> values, boolean isRole) {
        try {
            var cacheKey = buildCacheKey(id, accountType, isRole);
            if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(cacheKey))) {
                return values.stream().allMatch(key -> stringRedisTemplate.opsForSet().isMember(cacheKey, key));
            } else {
                var dbValues = isRole ? listRoles(id, accountType) : listPermissions(id, accountType);
                if (dbValues.isEmpty()) {
                    return false;
                } else {
                    add(cacheKey, dbValues);
                    return dbValues.containsAll(values);
                }
            }
        } catch (RedisException e) {
            log.error("redis 不可用: {}", e.getMessage());
            return false;
        }
    }

    protected boolean hasAny(@NotNull ID id, long accountType, @NotNull Collection<String> values, boolean isRole) {
        try {
            var cacheKey = buildCacheKey(id, accountType, isRole);
            if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(cacheKey))) {
                return values.stream().anyMatch(key -> stringRedisTemplate.opsForSet().isMember(cacheKey, key));
            } else {
                var dbValues = isRole ? listRoles(id, accountType) : listPermissions(id, accountType);
                if (dbValues.isEmpty()) {
                    return false;
                } else {
                    add(cacheKey, dbValues);
                    return CollUtil.containsAny(values, dbValues);
                }
            }
        } catch (RedisException e) {
            log.error("redis 不可用: {}", e.getMessage());
            return false;
        }
    }
}
